<?php

/*
 * Copyright (C) 2009 - 2011 Pham Cong Dinh
 *
 * This file is part of Spica.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

include_once 'library/spica/core/validator/Common.php';

/**
 * A validator that can contain other validators. This class is extended by
 * two subclasses: SpicaOrCompositeValidator and SpicaAndCompositeValidator
 *
 * @category   spica
 * @package    core
 * @subpackage validator\complex
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.2
 * @since      March 22, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Complex.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
abstract class SpicaCompositeValidator extends SpicaValidator
{
    /**
     * Array of validators that constitute this composite validator.
     *
     * @var array SpicaValidator[]
     */
    protected $_validators = array();

    /**
     * Creates an object of <code>SpicaCompositeValidator</code>.
     *
     * @param array $validators SpicaValidator[] array of validators that constitute this composite validator
     * @param bool  $required Determines whether this value is required. Defaults to true
     */
    public function __construct($validators, $required = true)
    {
        $this->_required   = (bool) $required;
        $this->_validators = $validators;
    }

    /**
     * Adds a validator into the set.
     *
     * @param SpicaValidatable $validator
     */
    public function add(SpicaValidatable $validator)
    {
        $this->_validators[] = $validator;
    }

    /**
     * (non-PHPdoc)
     * @see trunk/library/spica/core/validator/SpicaValidator#isValid()
     */
    public function isValid($value = null)
    {
        throw new BadMethodCallException('This method can not be inherited directly. It is intended to be re-implemented. ');
    }

    /**
     * Gets the validators that constitute this composite validator.
     *
     * @return array SpicaValidator[]
     */
    public function getValidators()
    {
        return $this->_validators;
    }

    /**
     * Returns all violation messages.
     *
     * @return array violation messages
     */
    public function getViolationMessages()
    {
        return (array) $this->_violationMessage;
    }
}

/**
 * This validator creates a composite validator.
 * Validation requires that at least one of the validators that
 * make up the composite validator be true.
 *
 * For example, you can use the SpicaOrCompositeValidator to require that
 * the SpicaNotNullValidator OR the SpicaDateRangeValidator be true.
 *
 * @category   spica
 * @package    core
 * @subpackage validator\complex
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.2
 * @since      March 22, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Complex.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaOrCompositeValidator extends SpicaCompositeValidator
{
    /**
     * Creates an object of <code>SpicaOrCompositeValidator</code>.
     *
     * @param array  $validators SpicaValidator[] array of validators that constitute this composite validator
     * @param string $violationMessage violation message to use if the validation fails
     * @param bool   $required Determines whether this value is required. Defaults to true
     */
    public function __construct($validators, $violationMessage, $required = true)
    {
        $this->_required         = (bool) $required;
        $this->_validators       = $validators;
        $this->_violationMessage = $violationMessage;
    }

    /**
     * (non-PHPdoc)
     * @see trunk/library/spica/core/validator/SpicaCompositeValidator#isValid()
     */
    public function isValid($value = null)
    {
        $this->_value = $value;

        // No data is provided but this value is optional (not mandatory)
        if (true === spica_valid_empty_string($value) && false === $this->_required)
        {
            return true;
        }

        $failed = 0;
        for ($i = 0, $length = count($this->_validators); $i < $length; $i++)
        {
            if (false === $this->_validators[$i]->isValid($value))
            {
                $failed++;
            }
        }

        // None of validators is passed
        if ($failed === $length)
        {
            return false;
        }

        return true;
    }

    /**
     * (non-PHPdoc)
     * @see trunk/library/spica/core/validator/SpicaCompositeValidator#getViolationMessages()
     */
    public function getViolationMessages()
    {
        $messages = array();

        for ($i = 0, $length = count($this->_validators); $i < $length; $i++)
        {
            $messages[] = $this->_validators[$index]->getViolationMessage();
        }

        return $messages;
    }
}

/**
 * This validator creates a composite validator. Validity of the composite
 * implies validity of the all validators it is composed of must be valid.
 *
 * @category   spica
 * @package    core
 * @subpackage validator\complex
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.2
 * @since      March 22, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Complex.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaAndCompositeValidator extends SpicaCompositeValidator
{
    /**
     * (non-PHPdoc)
     * @see trunk/library/spica/core/validator/SpicaCompositeValidator#isValid()
     */
    public function isValid($value = null)
    {
        $this->_value = $value;

        // No data is provided but this value is optional (not mandatory)
        if (true === spica_valid_empty_string($value) && false === $this->_required)
        {
            return true;
        }

        foreach ($this->_validators as $validator)
        {
            if (false === $validator->isValid($value))
            {
                $this->_violationMessage = $validator->getViolationMessage();
                return false;
            }
        }

        return true;
    }
}

/**
 * The SpicaBooleanCompositeValidator allows you to chain (compose) many
 * validators to validate one field. The contained validators may be
 * required to all validate the value to validate or it may be enough that
 * one contained validator validates the value. This behaviour is controlled
 * by the modes AND, OR, NOT, XOR.
 *
 * This validator allows you to build complex boolean expressions like:
 *
 * (Condition1 AND Condition2 OR Condition3 XOR Condition4 AND NOT Condition5)
 *
 * or
 *
 * (Condition1 OR (Condition2 AND Condition3) OR (NOT (Condition4 AND Condition5 AND Condition6))).
 *
 * @category   spica
 * @package    core
 * @subpackage validator\complex
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      March 30, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Complex.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaBooleanCompositeValidator extends SpicaCompositeValidator
{
    /**
     * Sets of validators.
     *
     * @var array
     */
    protected $_validators = array();

    /**
     * Validator count.
     *
     * @var int
     */
    protected $_validatorCount = 0;

    /**
     * Sets of operators.
     *
     * @var array
     */
    protected $_operators = array();

    /**
     * Validation results.
     *
     * @var array
     */
    protected $_validationResults = array();

    /**
     * Constructs a boolean expression validator object with a contained seed validator.
     *
     * @param string $violationMessage violation message to use if the validation fails
     */
    public function __construct($violationMessage)
    {
        $this->_violationMessage = $violationMessage;
    }

    /**
     * Returns true if one or more of the contained validators return true.
     *
     * @param  SpicaValidator $validator
     * @return SpicaBooleanCompositeValidator
     */
    public function OR_(SpicaValidatable $validator)
    {
        $this->_validators[] = $validator;

        if ($this->_validatorCount > 0)
        {
            $this->_operators[] = ' || ';
        }

        $this->_validatorCount++;

        return $this;
    }

    /**
     * Returns true only if all contained validators return true.
     *
     * @param  SpicaValidator $validator
     * @return SpicaBooleanCompositeValidator
     */
    public function AND_(SpicaValidatable $validator)
    {
        $this->_validators[] = $validator;

        if ($this->_validatorCount > 0)
        {
            $this->_operators[] = ' && ';
        }

        $this->_validatorCount++;

        return $this;
    }

    /**
     * Returns true if only one of the contained validators return true.
     *
     * @param  SpicaValidator $validator
     * @return SpicaBooleanCompositeValidator
     */
    public function XOR_(SpicaValidatable $validator)
    {
        $this->_validators[] = $validator;

        if ($this->_validatorCount > 0)
        {
            $this->_operators[] = ' XOR ';
        }

        $this->_validatorCount++;

        return $this;
    }

    /**
     * Appends AND !(operand) expression.
     *
     * @param  SpicaValidator $validator
     * @return SpicaBooleanCompositeValidator
     */
    public function AND_NOT(SpicaValidatable $validator)
    {
        $this->_validators[] = $validator;

        if ($this->_validatorCount > 0)
        {
            $this->_operators[] = ' && !';
        }

        $this->_validatorCount++;

        return $this;
    }

    /**
     * Appends OR !(operand) expression.
     *
     * @param  SpicaValidator $validator
     * @return SpicaBooleanCompositeValidator
     */
    public function OR_NOT(SpicaValidatable $validator)
    {
        $this->_validators[] = $validator;

        if ($this->_validatorCount > 0)
        {
            $this->_operators[] = ' || !';
        }

        $this->_validatorCount++;

        return $this;
    }

    /**
     * Appends XOR !(operand) expression.
     *
     * @param  SpicaValidator $validator
     * @return SpicaBooleanCompositeValidator
     */
    public function XOR_NOT(SpicaValidatable $validator)
    {
        $this->_validators[] = $validator;

        if ($this->_validatorCount > 0)
        {
            $this->_operators[] = ' XOR !';
        }

        $this->_validatorCount++;

        return $this;
    }

    /**
     * Adds a validator into the set.
     *
     * @param  SpicaValidatable $validator
     * @return SpicaBooleanCompositeValidator
     */
    public function add(SpicaValidatable $validator)
    {
        return $this->AND_($validator);
    }

    /**
     * (non-PHPdoc)
     * @see trunk/library/spica/core/validator/SpicaCompositeValidator#isValid()
     */
    public function isValid($value = null)
    {
        $this->_value = $value;

        // Boolean expression
        $exp    = '';

        for ($i = 0; $i < $this->_validatorCount; $i++)
        {
            $result   = (int) $this->_validators[$i]->isValid($value);

            if (true  === isset($this->_operators[$i]))
            {
                $exp .= $result.$this->_operators[$i];
            }
            else
            {
                $exp .= $result;
            }

            $this->_validationResults[$i] = $result;
        }

        if (true === empty($exp))
        {
            return false;
        }

        return (bool) eval('return ('. $exp. ');');
    }
}

/**
 * A validator that is associated with a condition that determines which sub-validators
 * should be applied on a given value or not. Remember that the order of conditions
 * to add will determines which condition is tested first.
 *
 * The sample scenario:
 *
 * + IF A is TRUE, execute validators: A AND B AND C
 * + ELSE
 *   IF B is TRUE, execute validators: D AND C
 *
 * where A and B is conditions to check
 *
 * @category   spica
 * @package    core
 * @subpackage validator\complex
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      March 30, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Complex.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaConditionalValidator extends SpicaValidator
{
    /**
     * Sets of conditions to check.
     *
     * @var array
     */
    protected $_conditions = array();

    /**
     * Determines whether there is no condition is met, validation can pass or not.
     *
     * @var bool <code>true</code> means that at least a met condition is required.
     */
    protected $_required;

    /**
     * Constructs a new <code>SpicaConditionalValidator</code>.
     *
     * @param string $violationMessage violation message to use if no condition is met or validation fails.
     * @param bool   $required Validation can pass or not if no condition is met,
     *                         default to <code>true</code>, means that at least a met condition is required.
     */
    public function __construct($violationMessage = null, $required = true)
    {
        $this->_required         = (bool) $required;
        $this->_violationMessage = $violationMessage;
    }

    /**
     * Adds a condition of this conditional validator.
     *
     * @param SpicaValidatorCondition $condition The condition to add.
     * @param SpicaValidator          $validator The validator to do validation if condition is met.
     * @param string $violationMessage
     */
    public function addCondition($condition, $validator, $violationMessage)
    {
        $this->_conditions[] = array(
          'condition'        => $condition,
          'validator'        => $validator,
          'violationMessage' => $violationMessage,
        );
    }

    /**
     * (non-PHPdoc)
     * @see library/spica/core/validator/SpicaValidator#isValid()
     */
    public function isValid($value = null)
    {
        foreach ($this->_conditions as $name => $condition)
        {
            if (true === $condition['condition']->check($value))
            {
                return $condition['validator']->isValid($value);
            }
        }

        return !$this->_required;
    }

    /**
     * Gets set of conditions to check.
     *
     * @return array
     */
    public function getConditions()
    {
        return $this->_conditions;
    }

    /**
     * Determines whether there is no condition is met, validation can pass or not.
     *
     * @return bool <code>true</code> means that at least a met condition is required.
     */
    public function isRequired()
    {
        return $this->_required;
    }
}

/**
 * This class takes care of the logical operation of the condition.
 * It is highly recommended for condition implementation to sub-class this class
 * when possible.
 *
 * @category   spica
 * @package    core
 * @subpackage validator\complex
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 01, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Complex.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
abstract class SpicaValidatorCondition
{
    /**
     * Returns whether the given value adheres to this condition.
     *
     * Delegates to the {@link SpicaValidatorCondition#doCheck(Object)}.
     * Provides callback methods for sub-classes to intercept the checking.
     */
    public final function check($value = null)
    {
        $this->beforeObjectChecked($value);
        $result = $this->doCheck($value);
        return $this->afterObjectChecked($value, $result);
    }

    /**
     * Performs the actual checking of this condition on the checked object.
     *
     * @param mixed $value The value to be checked.
     * @return <code>true</code> if the given object adheres to this condition,
     *         <code>false</code> otherwise.
     */
    public abstract function doCheck($value = null);

    /**
     * A callback method that enables sub-classes intercept the value checking
     * before it is being checked.
     * Sub-classes may override this method and perform custom assertions and
     * prevent the checking by throwing an {@link InvalidArgumentException};
     * @param mixed $value
     */
    protected function beforeObjectChecked($value = null)
    {
        // no-op
    }

    /**
     * A callback method that enables sub-classes to intercept the object
     * checking after it was checked. Sub-classes can override this method and
     * change the check result. By default, this method returns the original check result.
     *
     * @param mixed $value          The checked object.
     * @param bool  $originalResult The original check result as returned by the
     *                              specific condition implementation.
     * @return The final check result.
     */
    protected function afterObjectChecked($value = null, $originalResult)
    {
        return $originalResult;
    }
}

?>